 /*******************************************************************************
  * Copyright (c) 2000, 2006 IBM Corporation and others.
  * All rights reserved. This program and the accompanying materials
  * are made available under the terms of the Eclipse Public License v1.0
  * which accompanies this distribution, and is available at
  * http://www.eclipse.org/legal/epl-v10.html
  *
  * Contributors:
  * IBM Corporation - initial API and implementation
  * Sebastian Davids <sdavids@gmx.de> - bug 77332 - [Markers] Add task dialog improvements
  *******************************************************************************/

 package org.eclipse.ui.views.markers.internal;

 import java.util.HashMap ;
 import java.util.Map ;

 import org.eclipse.core.commands.ExecutionException;
 import org.eclipse.core.commands.operations.IUndoableOperation;
 import org.eclipse.core.resources.IMarker;
 import org.eclipse.core.resources.IResource;
 import org.eclipse.core.resources.ResourcesPlugin;
 import org.eclipse.core.runtime.CoreException;
 import org.eclipse.core.runtime.IPath;
 import org.eclipse.jface.dialogs.Dialog;
 import org.eclipse.jface.dialogs.ErrorDialog;
 import org.eclipse.jface.dialogs.IDialogConstants;
 import org.eclipse.jface.dialogs.IDialogSettings;
 import org.eclipse.jface.dialogs.TrayDialog;
 import org.eclipse.osgi.util.NLS;
 import org.eclipse.swt.SWT;
 import org.eclipse.swt.events.ModifyEvent;
 import org.eclipse.swt.events.ModifyListener;
 import org.eclipse.swt.layout.GridData;
 import org.eclipse.swt.layout.GridLayout;
 import org.eclipse.swt.widgets.Composite;
 import org.eclipse.swt.widgets.Control;
 import org.eclipse.swt.widgets.Label;
 import org.eclipse.swt.widgets.Shell;
 import org.eclipse.swt.widgets.Text;
 import org.eclipse.ui.PlatformUI;
 import org.eclipse.ui.ide.undo.CreateMarkersOperation;
 import org.eclipse.ui.ide.undo.UpdateMarkersOperation;
 import org.eclipse.ui.ide.undo.WorkspaceUndoUtil;
 import org.eclipse.ui.internal.ide.IDEWorkbenchPlugin;

 /**
  * Shows the properties of a new or existing marker
  *
  * In 3.3, this class was refactored to allow pre-existing public dialog classes
  * to share the implementation. Note that certain methods are exposed as API
  * in public subclasses, so changes to the methods in this class should be
  * treated carefully as they may affect API methods in subclasses. The specific
  * methods affected are documented in the method comment.
  */
 public class DialogMarkerProperties extends TrayDialog {

     private static final String DIALOG_SETTINGS_SECTION = "DialogMarkerPropertiesDialogSettings"; //$NON-NLS-1$

     /**
      * The marker being shown, or <code>null</code> for a new marker
      */
     private IMarker marker = null;

     /**
      * The resource on which to create a new marker
      */
     private IResource resource = null;

     /**
      * The type of marker to be created
      */
     private String type = IMarker.MARKER;

     /**
      * The initial attributes to use when creating a new marker
      */
     private Map initialAttributes = null;

     /**
      * The text control for the Description field.
      */
     private Text descriptionText;

     /**
      * The control for the Creation Time field.
      */
     private Label creationTime;

     /**
      * The text control for the Resource field.
      */
     private Text resourceText;

     /**
      * The text control for the Folder field.
      */
     private Text folderText;

     /**
      * The text control for the Location field.
      */
     private Text locationText;

     /**
      * Dirty flag. True if any changes have been made.
      */
     private boolean dirty;

     private String title;
     
     /**
      * The name used to describe the specific kind of marker. Used when
      * creating an undo command for the dialog, so that a specific name such
      * as "Undo Create Task" or "Undo Modify Bookmark" can be used.
      */
     private String markerName;

     /**
      * Creates the dialog. By default this dialog creates a new marker. To set
      * the resource and initial attributes for the new marker, use
      * <code>setResource</code> and <code>setInitialAttributes</code>. To
      * show or modify an existing marker, use <code>setMarker</code>.
      *
      * @param parentShell
      * the parent shell
      */
     public DialogMarkerProperties(Shell parentShell) {
         super(parentShell);
         setShellStyle(getShellStyle() | SWT.RESIZE);
     }

     /**
      * Creates the dialog. By default this dialog creates a new marker. To set
      * the resource and initial attributes for the new marker, use
      * <code>setResource</code> and <code>setInitialAttributes</code>. To
      * show or modify an existing marker, use <code>setMarker</code>.
      *
      * @param parentShell
      * the parent shell
      * @param title
      * the title of the dialog
      */
     public DialogMarkerProperties(Shell parentShell, String title) {
         super(parentShell);
         setShellStyle(getShellStyle() | SWT.RESIZE);
         this.title = title;
     }
     
     /**
      * Creates the dialog. By default this dialog creates a new marker. To set
      * the resource and initial attributes for the new marker, use
      * <code>setResource</code> and <code>setInitialAttributes</code>. To
      * show or modify an existing marker, use <code>setMarker</code>.
      *
      * @param parentShell
      * the parent shell
      * @param title
      * the title of the dialog
      * @param markerName
      * the name used to describe the specific kind of marker shown
      *
      * @since 3.3
      */
     public DialogMarkerProperties(Shell parentShell, String title, String markerName) {
         super(parentShell);
         setShellStyle(getShellStyle() | SWT.RESIZE);
         this.title = title;
         this.markerName = markerName;
     }

     /**
      * Sets the marker to show or modify.
      * <p>IMPORTANT: Although this class is internal, there are public
      * subclasses that expose this method as API. Changes in
      * this implementation should be treated as API changes.
      *
      * @param marker the marker, or <code>null</code> to create a new marker
      *
      * @since 3.3
      */
     public void setMarker(IMarker marker) {
         this.marker = marker;
         if (marker != null) {
             try {
                 type = marker.getType();
             } catch (CoreException e) {
             }
         }
     }

     /**
      * Returns the marker being created or modified.
      * For a new marker, this returns <code>null</code> until
      * the dialog returns, but is non-null after.
      * <p>IMPORTANT: Although this method is protected and the class is
      * internal, there are public subclasses that expose this method as API.
      * Changes in this implementation should be treated as API changes.
      *
      * @return the marker
      *
      * @since 3.3
      */
     protected IMarker getMarker() {
         return marker;
     }

     /**
      * Sets the resource to use when creating a new task.
      * If not set, the new task is created on the workspace root.
      * <p>IMPORTANT: Although this class is internal, there are public
      * subclasses that expose this method as API. Changes in
      * this implementation should be treated as API changes.
      *
      * @param resource the resource
      */
     public void setResource(IResource resource) {
         this.resource = resource;
     }

     /**
      * Returns the resource to use when creating a new task,
      * or <code>null</code> if none has been set.
      * If not set, the new task is created on the workspace root.
      * <p>IMPORTANT: Although this method is protected and the class is
      * internal, there are public subclasses that expose this method as API.
      * Changes in this implementation should be treated as API changes.
      *
      * @return the resource
      *
      * @since 3.3
      */
     protected IResource getResource() {
         return resource;
     }

     /**
      * Sets initial attributes to use when creating a new task.
      * If not set, the new task is created with default attributes.
      * <p>IMPORTANT: Although this method is protected and the class is
      * internal, there are public subclasses that expose this method as API.
      * Changes in this implementation should be treated as API changes.
      *
      * @param initialAttributes the initial attributes
      *
      * @since 3.3
      */
     protected void setInitialAttributes(Map initialAttributes) {
         this.initialAttributes = initialAttributes;
     }

     /**
      * Returns the initial attributes to use when creating a new task,
      * or <code>null</code> if not set.
      * If not set, the new task is created with default attributes.
      * <p>IMPORTANT: Although this method is protected and the class is
      * internal, there are public subclasses that expose this method as API.
      * Changes in this implementation should be treated as API changes.
      *
      * @return the initial attributes
      *
      * @since 3.3
      */
     protected Map getInitialAttributes() {
         if (initialAttributes == null) {
             initialAttributes = new HashMap ();
         }
         return initialAttributes;
     }

     /**
      * Method declared on Window.
      */
     protected void configureShell(Shell newShell) {
         super.configureShell(newShell);
         if (title == null) {
             newShell.setText(MarkerMessages.propertiesDialog_title);
         } else {
             newShell.setText(title);
         }
     }

     /**
      * Method declared on Dialog.
      */
     protected Control createDialogArea(Composite parent) {
         // initialize resources/properties
 if (marker != null) {
             resource = marker.getResource();
             try {
                 initialAttributes = marker.getAttributes();
             } catch (CoreException e) {
             }
         } else if (resource == null) {
             resource = ResourcesPlugin.getWorkspace().getRoot();
         }

         Composite comp = (Composite) super.createDialogArea(parent);
         Composite composite = new Composite(comp, SWT.NULL);
         GridLayout layout = new GridLayout(2, false);
         layout.marginWidth = 0;
         layout.marginHeight = 0;
         composite.setLayout(layout);
         GridData gridData = new GridData(GridData.FILL_HORIZONTAL);
         composite.setLayoutData(gridData);
     
         initializeDialogUnits(composite);
         createDescriptionArea(composite);
         if (marker != null) {
             createSeperator(composite);
             createCreationTimeArea(composite);
         }
         createAttributesArea(composite);
         if (resource != null) {
             createSeperator(composite);
             createResourceArea(composite);
         }
         updateDialogFromMarker();
         updateEnablement();
         
         Dialog.applyDialogFont(composite);
         
         return composite;
     }

     /**
      * Creates a seperator.
      */
     protected void createSeperator(Composite parent) {
         Label seperator = new Label(parent, SWT.NULL);
         GridData gridData = new GridData(GridData.FILL_HORIZONTAL);
         gridData.horizontalSpan = 2;
         seperator.setLayoutData(gridData);
     }
     
     /**
      * Method createCreationTimeArea.
      * @param parent
      */
     private void createCreationTimeArea(Composite parent) {
         Label label = new Label(parent, SWT.NONE);
         label.setText(MarkerMessages
                 .propertiesDialog_creationTime_text);

         creationTime = new Label(parent, SWT.NONE);
     }

     /**
      * Creates the OK and Cancel buttons.
      */
     protected void createButtonsForButtonBar(Composite parent) {
         createButton(parent, IDialogConstants.OK_ID, IDialogConstants.OK_LABEL,
                 true);
         createButton(parent, IDialogConstants.CANCEL_ID,
                 IDialogConstants.CANCEL_LABEL, false);
     }

     /**
      * Creates the area for the Description field.
      */
     private void createDescriptionArea(Composite parent) {
         Label label = new Label(parent, SWT.NONE);
         label.setText(MarkerMessages.propertiesDialog_description_text);
         descriptionText = new Text(parent, (SWT.SINGLE | SWT.BORDER));
         GridData gridData = new GridData(GridData.FILL_HORIZONTAL);
         gridData.widthHint = convertHorizontalDLUsToPixels(400);
         descriptionText.setLayoutData(gridData);

         descriptionText.addModifyListener(new ModifyListener() {
             public void modifyText(ModifyEvent e) {
                 markDirty();
             }
         });
     }

     /**
      * This method is intended to be overridden by subclasses. The attributes
      * area is created between the creation time area and the resource area.
      *
      * @param parent
      * the parent composite
      */
     protected void createAttributesArea(Composite parent) {
     }

     /**
      * Creates the area for the Resource field.
      */
     private void createResourceArea(Composite parent) {
         Label resourceLabel = new Label(parent, SWT.NONE);
         resourceLabel.setText(MarkerMessages.propertiesDialog_resource_text);
         resourceText = new Text(parent, SWT.SINGLE | SWT.WRAP
                 | SWT.READ_ONLY | SWT.BORDER);
         GridData gridData = new GridData(GridData.FILL_HORIZONTAL);
         resourceText.setLayoutData(gridData);

         Label folderLabel = new Label(parent, SWT.NONE);
         folderLabel.setText(MarkerMessages.propertiesDialog_folder_text);
         folderText = new Text(parent, SWT.SINGLE | SWT.WRAP | SWT.READ_ONLY
                 | SWT.BORDER);
         gridData = new GridData(GridData.FILL_HORIZONTAL);
         folderText.setLayoutData(gridData);

         Label locationLabel = new Label(parent, SWT.NONE);
         locationLabel.setText(MarkerMessages.propertiesDialog_location_text);
         locationText = new Text(parent, SWT.SINGLE | SWT.WRAP
                 | SWT.READ_ONLY | SWT.BORDER);
         gridData = new GridData(GridData.FILL_HORIZONTAL);
         locationText.setLayoutData(gridData);
     }

     /**
      * Updates the dialog from the marker state.
      */
     protected void updateDialogFromMarker() {
         if (marker == null) {
             updateDialogForNewMarker();
             return;
         }
         descriptionText.setText(Util.getProperty(IMarker.MESSAGE, marker));
         if (creationTime != null) {
             creationTime.setText(Util.getCreationTime(marker));
         }
         if (resourceText != null) {
             resourceText.setText(Util.getResourceName(marker));
         }
         if (folderText != null) {
             folderText.setText(Util.getContainerName(marker));
         }
         if (locationText != null) {
             String line = Util.getProperty(IMarker.LINE_NUMBER, marker);
             if (line.equals("")) { //$NON-NLS-1$
 locationText.setText(""); //$NON-NLS-1$
 } else {
                 locationText.setText(NLS.bind(MarkerMessages.label_lineNumber, line));
             }
         }

         descriptionText.selectAll();
     }
     
     /**
      * Updates the dialog from the predefined attributes.
      */
     protected void updateDialogForNewMarker() {
         if (resource != null && resourceText != null && folderText != null) {
             resourceText.setText(resource.getName());

             IPath path = resource.getFullPath();
             int n = path.segmentCount() - 1; // n is the number of segments in container, not path
 if (n > 0) {
                 int len = 0;
                 for (int i = 0; i < n; ++i) {
                     len += path.segment(i).length();
                 }
                 // account for /'s
 if (n > 1) {
                     len += n - 1;
                 }
                 StringBuffer sb = new StringBuffer (len);
                 for (int i = 0; i < n; ++i) {
                     if (i != 0) {
                         sb.append('/');
                     }
                     sb.append(path.segment(i));
                 }
                 folderText.setText(sb.toString());
             }
         }

         if (initialAttributes != null) {
             Object description = initialAttributes.get(IMarker.MESSAGE);
             if (description != null && description instanceof String ) {
                 descriptionText.setText((String ) description);
             }
             descriptionText.selectAll();

             Object line = initialAttributes.get(IMarker.LINE_NUMBER);
             if (line != null && line instanceof Integer && locationText != null) {
                 locationText.setText(
                     NLS.bind(MarkerMessages.label_lineNumber, line));
             }
         }
     }
     
     /**
      * Method declared on Dialog
      */
     protected void okPressed() {
         if (marker == null || Util.isEditable(marker)) {
             saveChanges();
         }
         super.okPressed();
     }

     /**
      * Sets the dialog's dirty flag to <code>true</code>
      */
     protected void markDirty() {
         dirty = true;
     }

     /**
      * @return
      * <ul>
      * <li><code>true</code> if the dirty flag has been set to true.</li>
      * <li><code>false</code> otherwise.</li>
      * </ul>
      */
     protected boolean isDirty() {
         return dirty;
     }

     /**
      * Saves the changes made in the dialog if needed. Creates a new marker if
      * needed. Updates the existing marker only if there have been changes.
      */
     private void saveChanges() {
         Map attrs = getMarkerAttributes();
         IUndoableOperation op = null;
         if (marker == null) {
             if (resource == null)
                 return;
             op = new CreateMarkersOperation(type, attrs,
                     resource, getCreateOperationTitle());
         } else {
             if (isDirty()) {
                 op = new UpdateMarkersOperation(marker, attrs,
                         getModifyOperationTitle(), true);
             }
         }
         if (op != null) {
             try {
                 PlatformUI.getWorkbench()
                         .getOperationSupport()
                         .getOperationHistory().execute(op,
                                 null, WorkspaceUndoUtil.getUIInfoAdapter(getShell()));
             } catch (ExecutionException e) {
                 if (e.getCause() instanceof CoreException) {
                     ErrorDialog.openError(
                             getShell(),
                             MarkerMessages.Error, null, ((CoreException)e.getCause()).getStatus());
                 } else
                     IDEWorkbenchPlugin.log(e.getMessage(), e);
             }
         }
     }

     /**
      * Returns the marker attributes to save back to the marker, based on the
      * current dialog fields.
      */
     protected Map getMarkerAttributes() {
         Map attrs = getInitialAttributes();
         attrs.put(IMarker.MESSAGE, descriptionText.getText());
         return attrs;
     }
     
     /**
      * Updates widget enablement for the dialog. Should be overridden by
      * subclasses.
      */
     protected void updateEnablement() {
         descriptionText.setEditable(isEditable());
     }

     /**
      * @return
      * <ul>
      * <li><code>true</code> if the marker is editable or the dialog is
      * creating a new marker.</li>
      * <li><code>false</code> if the marker is not editable.</li>
      * </ul>
      */
     protected boolean isEditable() {
         if (marker == null) {
             return true;
         }
         return Util.isEditable(marker);
     }

     /**
      * Sets the marker type when creating a new marker.
      *
      * @param type
      * the marker type
      *
      * @since 3.3 this method is protected.
      */
     protected void setType(String type) {
         this.type = type;
     }
     
     /* (non-Javadoc)
      * @see org.eclipse.jface.window.Dialog#getDialogBoundsSettings()
      *
      * @since 3.2
      */
     protected IDialogSettings getDialogBoundsSettings() {
         IDialogSettings settings = IDEWorkbenchPlugin.getDefault().getDialogSettings();
         IDialogSettings section = settings.getSection(DIALOG_SETTINGS_SECTION);
         if (section == null) {
             section = settings.addNewSection(DIALOG_SETTINGS_SECTION);
         }
         return section;
     }
     
     /**
      * Return the string that describes a modify marker operation.
      * Subclasses may override to more specifically describe the marker.
      *
      * @since 3.3
      */
     protected String getModifyOperationTitle() {
         if (markerName == null) {
             // we don't know what kind of marker is being modified
 return MarkerMessages.DialogMarkerProperties_ModifyMarker;
         }
         return NLS.bind(MarkerMessages.qualifiedMarkerCommand_title,
                 MarkerMessages.DialogMarkerProperties_Modify, markerName);
     }
     
     /**
      * Return the string that describes a create marker operation.
      * Subclasses may override to more specifically describe the marker.
      *
      * @since 3.3
      */
     protected String getCreateOperationTitle() {
         if (markerName == null) {
             // we don't know what kind of marker is being created
 return MarkerMessages.DialogMarkerProperties_CreateMarker;
         }
         return NLS.bind(MarkerMessages.qualifiedMarkerCommand_title,
                 MarkerMessages.DialogMarkerProperties_Create, markerName);
         
     }
 }

